package tmt.usercenter.web.configure.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter;
import org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.session.*;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CompositeFilter;
import org.springframework.web.filter.CorsFilter;
import tmt.usercenter.web.configure.security.bean.*;

import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.List;

@Configuration
@EnableWebSecurity
@EnableOAuth2Client
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启全局方法安全验证
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Qualifier("oauth2ClientContext")
    @Autowired
    private OAuth2ClientContext oauth2ClientContext;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private TmtServerSessionRegistry tmtSessionRegistry;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 声明这个bean可以使能并发Session控制。
     * 例如： http.sessionManagement().maximumSessions(2)
     * .expiredUrl("/sessionExpired.html")
     * .invalidSessionUrl("/invalidSession.html");
     *
     * @return
     */
    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }

    /**
     * @param filter OAuth2ClientContextFilter，主要作用是重定向
     * @return
     */
    @Bean
    public FilterRegistrationBean oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(filter);
        registration.setOrder(-100);
        return registration;
    }

    /**
     * ajax通信是，遇到Session超时时的处理过滤器。
     *
     * @return
     */
    @Bean
    public Filter ajaxTimeoutRedirectFilter() {
        AjaxTimeoutRedirectFilter filter = new AjaxTimeoutRedirectFilter();
        return filter;
    }

    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
        return new RestTemplate(factory);
    }

    @Bean
    public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setReadTimeout(5000);//ms
        factory.setConnectTimeout(15000);//ms
        return factory;
    }

    @Primary
    @Bean
    public SessionRegistry sessionRegistry() {
        return tmtSessionRegistry;
    }

    @Bean
    @Order(1)
    public ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlAuthenticationStrategy() {
        ConcurrentSessionControlAuthenticationStrategy cscas = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
        cscas.setMaximumSessions(1);
        cscas.setExceptionIfMaximumExceeded(false);
        return cscas;
    }

    @Bean
    @Order(2)
    public SessionFixationProtectionStrategy sessionFixationProtectionStrategy() {
        return new SessionFixationProtectionStrategy();
    }

    @Bean
    @Order(3)
    public RegisterSessionAuthenticationStrategy registerSessionAuthenticationStrategy() {
        return new TmtRegisterSessionAuthenticationStrategy((TmtServerSessionRegistry) sessionRegistry());
    }

    @Bean
    public CompositeSessionAuthenticationStrategy compositeSessionAuthenticationStrategy() {
        List<SessionAuthenticationStrategy> sessionAuthenticationStrategies = new ArrayList<>();
        sessionAuthenticationStrategies.add(concurrentSessionControlAuthenticationStrategy());
        sessionAuthenticationStrategies.add(sessionFixationProtectionStrategy());
        sessionAuthenticationStrategies.add(registerSessionAuthenticationStrategy());
        return new CompositeSessionAuthenticationStrategy(sessionAuthenticationStrategies);
    }

    @Bean
    public SimpleUrlAuthenticationSuccessHandler successHandler() {
        SimpleUrlAuthenticationSuccessHandler handler = new SimpleUrlAuthenticationSuccessHandler();
        handler.setUseReferer(true);
        return handler;
    }

    @Bean
    public AuthenticationProvider tmtAuthenticationProvider(@Autowired PasswordEncoder passwordEncoder) {
        TmtAuthenticationProvider provider = new TmtAuthenticationProvider(passwordEncoder);
        provider.setUserDetailsService(userDetailsService);
        return provider;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder())
        ;
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
//        web.debug(true);

        // 过滤对应匹配路径下的请求，优于 HttpSecurity 中用拦截路径来过滤
        web.ignoring().antMatchers(
                "/asset/**",
                "/**/*.ico",
                "/error/**",
                "/filestore/**",
                "/api/reg/**"
        );

    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .sessionFixation().migrateSession()
                .sessionAuthenticationStrategy(compositeSessionAuthenticationStrategy())
        ;

        http.authorizeRequests()
                .antMatchers("/login/**",
                        "/forgot/**",
                        "/register/**",
                        "/mobile/**",
                        "/email/**",
                        "/external/logout/**",
                        "/external/register/**",
                        "/external/role/**",
                        "/external/sm/**",
                        "/external/openid/**",
                        "/external/exist/**",
                        "/external/user/**",
                        "/external/notify/**",
                        "/oauth/**"
                ).permitAll()
                .anyRequest().authenticated() //其他未匹配的所有请求都需要用户被认证

                //允许用户基于表单认证
                .and().formLogin()

                //定义认证的URL地址，默认为/login，登录成功后用户进入首页
                .loginPage("/login").defaultSuccessUrl("/").permitAll()

//				.and().httpBasic() //允许用户使用HTTP基本验证进行认证*/

                .and().cors()

                //指定登出跳转地址
                .and().logout()

                //设置登出地址，默认为/logout
                .logoutUrl("/logout")

                //设置登出后的跳转地址，默认为/login
                .logoutSuccessUrl("/login")
                .permitAll()

                .and().exceptionHandling().accessDeniedPage("/error/denied")

                //防跨站请求伪造
                .and().csrf().disable().headers().frameOptions().sameOrigin()

                //.and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .and().addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class)
                .addFilterAfter(ajaxTimeoutRedirectFilter(), ExceptionTranslationFilter.class)
        ;
    }

	/*private static CsrfTokenRepository csrfTokenRepository() {
		HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
		repository.setHeaderName("X-XSRF-TOKEN");
		return repository;
	}*/

   /* @Bean
    public CorsFilter corsFilter() {
        return new MyCorsFilter();
    }*/

    @Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.addAllowedOrigin("https://localhost:8443");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsFilter(urlBasedCorsConfigurationSource);
    }


    @Bean
    @ConfigurationProperties("github")
    public ClientResources github() {
        return new ClientResources();
    }

    @Bean
    @ConfigurationProperties("security.oauth2")
    public ClientResources facebook() {
        return new ClientResources();
    }

    private Filter ssoFilter() {
        CompositeFilter filter = new CompositeFilter();
        List<Filter> filters = new ArrayList<>();
        filters.add(ssoFilter(facebook(), "/login/facebook"));
        filter.setFilters(filters);
        return filter;
    }

    private Filter ssoFilter(ClientResources client, String path) {
        OAuth2RestTemplate template = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext);

        UserInfoTokenServices tokenServices = new UserInfoTokenServices(
                client.getResource().getUserInfoUri(), client.getClient().getClientId());
        tokenServices.setRestTemplate(template);

        OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(path);
        filter.setRestTemplate(template);
        filter.setTokenServices(tokenServices);
        return filter;
    }

}
